package com.zyg.asm;

import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
import org.objectweb.asm.commons.Method;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class AsmUnitTest {
    public static void main(String[] args) {
        try {
//            FileInputStream is = new FileInputStream("E:\\project\\zyg-jdk-note\\asm\\src\\main\\java\\com\\zyg\\asm\\InjectTest.class");
//            //获取一个分析器(读取文件中的class信息)
//            ClassReader classReader = new ClassReader(is);
//            ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
//            //开始插桩
//            //自定义类访问者
//            classReader.accept(new MyClassVisitor(Opcodes.ASM7, classWriter), ClassReader.EXPAND_FRAMES);
//            byte[] bytes = classWriter.toByteArray();

            byte[] bytes=AsmUnitTestDump.dump();
            FileOutputStream os = new FileOutputStream("E:\\project\\zyg-jdk-note\\asm\\src\\main\\java\\com\\zyg\\asm\\InjectTest2.class");
            os.write(bytes);
            os.close();
            //is.close();
        } catch (Exception e) {
            e.printStackTrace();
        }


    }


    static class MyClassVisitor extends ClassVisitor {

        public MyClassVisitor(int api) {
            super(api);
        }

        public MyClassVisitor(int api, ClassWriter classWriter) {
            super(api, classWriter);
        }

        /**
         * 自定义方法访问者（当有方法执行时，就会执行这个api）
         *
         * @param access
         * @param name
         * @param descriptor
         * @param signature
         * @param exceptions
         * @return
         */
        @Override
        public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
            MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
            return new MyMethodVisitor(api, methodVisitor, access, name, descriptor);
        }
    }

    /**
     * 分析方法的信息
     */
    static class MyMethodVisitor extends AdviceAdapter {

        /**
         * Constructs a new {@link AdviceAdapter}.
         *
         * @param api           the ASM API version implemented by this visitor. Must be one of {@link
         *                      Opcodes#ASM4}, {@link Opcodes#ASM5}, {@link Opcodes#ASM6} or {@link Opcodes#ASM7}.
         * @param methodVisitor the method visitor to which this adapter delegates calls.
         * @param access        the method's access flags (see {@link Opcodes}).
         * @param name          the method's name.
         * @param descriptor    the method's descriptor (see {@link Type Type}).
         */
        protected MyMethodVisitor(int api, MethodVisitor methodVisitor, int access, String name, String descriptor) {
            super(api, methodVisitor, access, name, descriptor);
        }

        int s;

        /**
         * 方法进入的逻辑
         */
        @Override
        protected void onMethodEnter() {
            super.onMethodEnter();
            if(!inject){
                return;
            }
            //插入方法进入时的逻辑
//            long start = System.currentTimeMillis();
//            INVOKESTATIC java/lang/System.currentTimeMillis ()J
//            LSTORE 1
            //算好的结果暂存在操作数栈里面
            invokeConstructor(Type.getType("Ljava/lang/System;"), new Method("currentTimeMillis", "()J"));
            //将操作数栈的的计算结果存到变量表里下标为1的位置
            //从栈里面去拿一个Long类型的数据，拿到后存到本地
            s = newLocal(Type.LONG_TYPE);
            storeLocal(s);
        }

        int t;

        /**
         * 方法退出的逻辑
         *
         * @param opcode
         */
        @Override
        protected void onMethodExit(int opcode) {
            super.onMethodExit(opcode);
            if(!inject){
                return;
            }
            //插入方法退出的逻辑
//            long end = System.currentTimeMillis();

//            INVOKESTATIC java/lang/System.currentTimeMillis ()J
//            LSTORE 4
            invokeConstructor(Type.getType("Ljava/lang/System"), new Method("currentTimeMillis", "()J"));
            t = newLocal(Type.LONG_TYPE);
            storeLocal(t);

//            System.out.println("execute time " + (end - start));
//            GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
            getStatic(Type.getType("Ljava/lang/System"), "out", Type.getType("Ljava/io/PrintStream;"));
//            NEW java/lang/StringBuilder
            newInstance(Type.getType("Ljava/lang/StringBuilder;"));
//            DUP(复制引用，调用另一个方法，即另外方法的入栈)
            dup();
//            INVOKESPECIAL java/lang/StringBuilder.<init> ()V
            invokeConstructor(Type.getType("Ljava/lang/StringBuilder;"), new Method("<init>", "()V"));
//            LDC "execute time "(从常量池获取)
            visitLdcInsn("execute time ");
//            INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
            invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"), new Method("append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;"));
//            LLOAD 4
//            LLOAD 1
            //读数据到操作数栈
            loadLocal(s);
            loadLocal(t);
//            LSUB
            //减法
            math(SUB, Type.LONG_TYPE);
//            INVOKEVIRTUAL java/lang/StringBuilder.append (J)Ljava/lang/StringBuilder;
            invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"), new Method("append", "(J)Ljava/lang/StringBuilder;"));
//            INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
            invokeVirtual(Type.getType("Ljava/lang/StringBuilder;"), new Method("toString", "()Ljava/lang/String;"));
//            INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
            invokeVirtual(Type.getType("Ljava/io/PrintStream"), new Method("println", "(Ljava/lang/String;)V"));
        }

        boolean inject = false;

        @Override
        public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) {
            System.out.println("descriptor====> " + descriptor);
            if ("Lcom/zyg/asm/AsmAnnotation".equals(descriptor)) {
                inject = true;
            }
            return super.visitAnnotation(descriptor, visible);
        }
    }
}
